package com.xiyuan.smartutils.reflect;

import com.xiyuan.smartutils.Resources;
import com.xiyuan.smartutils.Systems;
import com.xiyuan.smartutils.loader.SystemLoader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AllPermission;
import java.security.Permissions;
import java.security.ProtectionDomain;

/**
 * @author xiyuan-lgz 2025-05-27 @version v1.0.0 新建与整理
 */

@SuppressWarnings({"unchecked"})
final class JavaUnsafe {
    
    private static Object javaUnsafe;
    private static Unsafe unsafe;
    private static final int J_VERSION;
    
    private static native Object getJavaUnsafe0(String className);
    
    private static native Class<?> defineClass0(String name, byte[] bytes, int off, int len, ClassLoader loader,
                                                ProtectionDomain protectionDomain);
    
    private static native void setAccessibleC(Constructor<?> constructor, boolean accessible);
    
    private static native void setAccessibleF(Field field, boolean accessible);
    
    private static native void setAccessibleM(Method method, boolean accessible);
    
    private static native void addOpensOrExports0(Object source, String pkg, Object target, boolean open, boolean syncVM);
    
    /**
     * addOpens
     *
     * @param source source module
     * @param pkg    source package
     * @param target target module
     */
    static void addOpens(Object source, String pkg, Object target) {
        addOpensOrExports(source, pkg, target, true, true);
    }
    
    /**
     * addOpens
     *
     * @param source source module
     * @param pkg    source package
     * @param target target module
     * @param open   true: open, false: export
     */
    public static void addOpensOrExports(Object source, String pkg, Object target, boolean open) {
        addOpensOrExports(source, pkg, target, open, true);
    }
    
    /**
     * addOpens
     *
     * @param source source module
     * @param pkg    source package
     * @param target target module
     * @param open   true: open, false: export
     * @param syncVM true: syncVM, false: not syncVM
     */
    public static void addOpensOrExports(Object source, String pkg, Object target, boolean open, boolean syncVM) {
        if (J_VERSION <= 8) {
            return;
        }
        if (source == null) {
            throw new NullPointerException("Source module is not null");
        }
        Class<?> moduleClass = source.getClass();
        if (source instanceof Class) {
            moduleClass = ((Class<?>) source);
        }
        String module = "java.lang.Module";
        if (!module.equals(moduleClass.getName())) {
            throw new IllegalArgumentException("source module is not java.lang.Module");
        }
        
        if (pkg == null) {
            throw new IllegalArgumentException("package is null");
        }
        if (target == null) {
            throw new NullPointerException("Target module is not null");
        }
        if (pkg.isEmpty()) {
            throw new IllegalArgumentException("package is empty");
        }
        if (!module.equals(target.getClass().getName())) {
            throw new IllegalArgumentException("target module is not java.lang.Module");
        }
        
        addOpensOrExports0(source, pkg, target, open, syncVM);
    }
    
    public static void setAccessible(Constructor<?> constructor, boolean flag) {
        if (!constructor.isAccessible()) {setAccessibleC(constructor, flag);}
    }
    
    public static void setAccessible(Field field, boolean flag) {
        if (!field.isAccessible()) { setAccessibleF(field, flag);}
    }
    
    public static void setAccessible(Method method, boolean flag) {
        if (!method.isAccessible()) {setAccessibleM(method, flag);}
    }
    
    public static Object getJavaUnsafe(int version) {
        SystemLoader.init();// 初始化
        if (javaUnsafe == null) {
            javaUnsafe = getJavaUnsafe0(version > 8 ? "jdk/internal/misc/Unsafe" : "sun/misc/Unsafe");
        }
        return javaUnsafe;
    }
    
    public static Unsafe getUnsafe() {
        return unsafe;
    }
    
    private static Unsafe initUnsafe() throws Throwable {
        
        Class<Unsafe> cls = null;
        Throwable throwable = null;
        String className = Unsafe.class.getPackage().getName() + ".internal.UnsafeImpl";
        String path = className.replaceAll("\\.", "/") + ".class";
        if (unsafe != null && unsafe.getClass().getName().equals(className)) {
            return unsafe;
        }
        try {
            byte[] input = Resources.getResourceBytes(path);
            if (input != null) {
                Permissions permissions = new Permissions();
                permissions.add(new AllPermission());
                ProtectionDomain protectionDomain = new ProtectionDomain(null, permissions);
                
                ClassLoader loader = Thread.currentThread().getContextClassLoader();
                cls = (Class<Unsafe>) defineClass0(className, input, 0, input.length, loader, protectionDomain);
            }
        }
        catch (Throwable e) {
        }
        
        if (cls == null) {
            throw new ClassNotFoundException("class UnsafeImpl not found");
        }
        
        Constructor<Unsafe> constructor = cls.getDeclaredConstructor(Object.class);
        setAccessibleC(constructor, true);
        unsafe = constructor.newInstance(javaUnsafe);
        return unsafe;
    }
    
    static Unsafe init() throws Throwable {
        getJavaUnsafe(J_VERSION);
        return initUnsafe();
    }
    
    static {
        try {
            J_VERSION = Systems.JAVA_MAIN_VERSION;
            init();
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (Throwable e) {
            throw new RuntimeException(e);
        }
        
    }
}
